package interfaz2;

import java.io.FileNotFoundException;
import java.util.Timer;
import java.util.TimerTask;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.ShellAdapter;
import org.eclipse.swt.events.ShellEvent;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.MessageBox;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.ToolItem;
import org.eclipse.swt.widgets.Tree;

import core.Util;

import dustconf.DustConf;
import dustlog.LogController;

/**
 * The view of Dust. This class implements how Dust is shown, if you want to
 * change how Dust looks you should start looking here.
 * 
 * @author ole
 */
public class DustInterface {
	public static LogController logger;
	
	// Class constants.
	public static final int N_VIEWS = 2;
	public static final int SUBSCRIPTION_VIEW = 0;
	public static final int PUBLICATION_VIEW = 1;
	public static final int LOG_VIEW = 2;
	public static final int CONF_VIEW = 3;
	public static final int DEFAULT_VIEW = SUBSCRIPTION_VIEW;
	// An hour in milliseconds.
	public static final int HOUR = 1000 * 60 * 60;
	
	// Class attributes.
	private Shell shell = null;
	private MultiViewComposite bottom_comp = null;
	private Timer updates_timer;
	
	/*
	 * Private classes. Mainly to handle events of DustInterface.
	 */
	
	private class AboutToolListener extends SelectionAdapter {
		@Override
		public void widgetSelected(SelectionEvent event) {
			AboutDialog about_dialog;
			
			try {
				about_dialog = new AboutDialog(shell);
				about_dialog.runDialog();	
			}
			// Occurs when it cannot found the image logo-i64.png.
			catch (FileNotFoundException e) {
				e.printStackTrace();
			}
		}
	}
	
	/**
	 * Class that handle when the user wants to close Dust.
	 */
	private class AskToCloseListener extends ShellAdapter {
		
		/*
		 * Private methods.
		 */
		
		/**
		 * When we have to close Dust some work must be done before do it. This
		 * method will do all that work.
		 */
		private void prepareToClose() {
			SubscriptionComposite subs_comp;
			
			// Stop every updating task.
			subs_comp = (SubscriptionComposite)bottom_comp.getChildren()[SUBSCRIPTION_VIEW];
			subs_comp.terminateUpdate();
			
			// Stop the timer.
			updates_timer.cancel();
		}
		
		/*
		 * Public methods.
		 */
		
		/**
		 * This method will be called when the user ask to close Dust.
		 */
		@Override
		public void shellClosed(ShellEvent event) {
			MessageBox mbox;
			int response;
			
			mbox = new MessageBox(shell,
								  SWT.ICON_QUESTION | SWT.OK | SWT.CANCEL);
			mbox.setMessage("Are you sure you want to exit?.");
			mbox.setText("Don't kill Dust!");
			response = mbox.open();
			
			// If the answer is OK tell the SWT to close Dust through the
			// "doit" attribute.
			if (response != SWT.OK) {
				event.doit = false;
				return;
			}
			
			// The answer was OK, prepare to close.
			prepareToClose();
		}
	}
	
	/**
	 * Class that handles the configuration ToolItem events.
	 */
	private class ConfigurationToolListener extends SelectionAdapter {
		@Override
		public void widgetSelected(SelectionEvent event) {
			bottom_comp.changeView(CONF_VIEW);
		}
	}
	
	/**
	 * Class that handles the log ToolItem events.
	 */
	private class LogToolListener extends SelectionAdapter {
		@Override
		public void widgetSelected(SelectionEvent event) {
			bottom_comp.changeView(LOG_VIEW);
		}
	}
	
	/**
	 * Class that handles the subscription ToolItem events.
	 */
	private class SubscriptionToolListener extends SelectionAdapter {
		@Override
		public void widgetSelected(SelectionEvent event) {
			bottom_comp.changeView(SUBSCRIPTION_VIEW);
		}
	}
	
	/**
	 * Class that handles the publication ToolItem events.
	 * 
	 * @author: ole
	 */
	private class PublicationToolListener extends SelectionAdapter {
		@Override
		public void widgetSelected(SelectionEvent event) {
			bottom_comp.changeView(PUBLICATION_VIEW);
		}
	}
	
	/**
	 * This class implement the task that will be executed every hour to update
	 * the blogs silently.
	 * 
	 * NOTE: Hey man, wtf are you doing here? why are you extending TimerTask
	 * (a Runnable) and then creating a new class that (again) extends
	 * Runnable?. It's always the same as you maybe have seen inside Dust again
	 * with other classes extending Runnable. Since the update procedure will
	 * need modify UI objects (the tree, the views, etc...) we have to execute
	 * it in the UI-thread, that's the reason to make TimerTask ask the
	 * UI-thread to execute another Runnable, because asyncExec() needs a
	 * Runnable. This other Runnable is this private class I'm using here.
	 * 
	 * @author ole
	 */
	private class UpdatePeriodicallyTask extends TimerTask {
		private class MakeTheUpdate implements Runnable {
			@Override
			public void run() {
				SubscriptionComposite subs_comp;
				Tree blogs_tree;
				BlogController blog_ctrl;
				
				// Update the Blogs.
				subs_comp = (SubscriptionComposite)bottom_comp.getChildren()[SUBSCRIPTION_VIEW];
				blogs_tree = subs_comp.getBlogsTree();
				for (int i = 0; i < blogs_tree.getItemCount(); i++) {
					if (blogs_tree.getItem(i).getData() instanceof BlogController) {
						blog_ctrl = (BlogController)blogs_tree.getItem(i).getData();
						blog_ctrl.forceUpdate();
					}
				}
			}
		}
		
		@Override
		public void run() {
			shell.getDisplay().asyncExec(new MakeTheUpdate());
		}
	}
	
	/*
	 * Private methods of DustInterface class.
	 */
	
	/**
	 * This method adds to the top toolbar of the Dust GUI the possible
	 * options they can do. This options will control the view at the bottom
	 * of the main window.
	 * 
	 * @param toolbar:
	 * 		The ToolBar to add the ToolItems.
	 * @throws FileNotFoundException 
	 */
	private void buildTools(ToolBar toolbar) throws FileNotFoundException {
		ToolItem toolitem;
		String images_path;
		
		images_path = Util.checkDirectoryPath(DustConf.getImagesFolder());
		
		// Create the tools for the toolbar.
		toolitem = new ToolItem(toolbar, SWT.NONE);
		toolitem.setText("Subscription");
		toolitem.setImage(IfaceUtils.buildImage(images_path + "subscribe.png"));
		toolitem.addSelectionListener(new SubscriptionToolListener());
		
		// Create a tool for publishing blogs and posts.
		toolitem = new ToolItem(toolbar, SWT.NONE);
		toolitem.setText("Publication");
		toolitem.setImage(IfaceUtils.buildImage(images_path + "people.png"));
		toolitem.addSelectionListener(new PublicationToolListener());
		
		// Create a tool to see the log.
		toolitem = new ToolItem(toolbar, SWT.NONE);
		toolitem.setText("       Log       ");
		toolitem.setImage(IfaceUtils.buildImage(images_path + "log.png"));
		toolitem.addSelectionListener(new LogToolListener());
		
		// Create a tool for the configuration.
		toolitem = new ToolItem(toolbar, SWT.NONE);
		toolitem.setText("Configuration");
		toolitem.setImage(IfaceUtils.buildImage(images_path + "configure.png"));
		toolitem.addSelectionListener(new ConfigurationToolListener());
		toolitem.setEnabled(false);
		
		// Create a tool for the help.
		toolitem = new ToolItem(toolbar, SWT.NONE);
		toolitem.setText("       About       ");
		toolitem.setImage(IfaceUtils.buildImage(images_path + "about.png"));
		toolitem.addSelectionListener(new AboutToolListener());
	}
	
	/**
	 * Builds everything the main Dust window needs.
	 * 
	 * @param shell
	 * 		The SWT shell where every other widget will be inside.
	 * @throws Exception 
	 */
	private void buildInterface() throws Exception {
		SubscriptionComposite subs_comp;
		PublicationComposite publ_comp;
		LogView log_comp;
		ConfigurationComposite conf_comp;
		ToolBar toolbar;
		
		// At the top a toolbar with the possible options.
		toolbar = new ToolBar(shell, SWT.HORIZONTAL);
		toolbar.setLayout(new FillLayout());
		buildTools(toolbar);
		
		// Below the toolbar we have toolitem-dependant views. Here we build
		// them hidden. When a toolitem is selected the proper view will be
		// shown.
		bottom_comp = new MultiViewComposite(shell, SWT.NONE);
		
		// Subscription view.
		subs_comp = new SubscriptionComposite(bottom_comp, SWT.BORDER);
		subs_comp.hide();
		
		// Publication view.
		publ_comp = new PublicationComposite(bottom_comp, SWT.BORDER);
		publ_comp.hide();
		
		// Log view.
		log_comp = new LogView(bottom_comp, SWT.BORDER);
		log_comp.hide();
		
		// Configuration view.
		conf_comp = new ConfigurationComposite(bottom_comp);
		conf_comp.hide();
		
		// Set the view for the LogController so, from now every Dust component
		// can log messages.
		LogController.setView(log_comp);
		
		// Show the default view.
		bottom_comp.changeView(DEFAULT_VIEW);
	}
	
	/*
	 * Public methods of DustInterface class.
	 */

	/**
	 * Constructor of DustInterface.
	 * 
	 * @param config
	 * 		The Dust configuration.
	 * @throws Exception
	 */
	public DustInterface() throws Exception {
		SubscriptionComposite subs_comp;
		Tree blogs_tree;
		BlogController blog_ctrl;
		
		shell = new Shell(new Display());
		
		// Set some properties of the shell.
		shell.setMaximized(true);
		shell.setText("Dust");
		shell.setLayout(new GridLayout());
		shell.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
		shell.addShellListener(new AskToCloseListener());
		
		// Add all the controls to our main window.
		buildInterface();
		
		/** TODO: We make an update here because it is a user requirement
	     * (Chema wants it :). The idea is get the blogs updated when Dust
	     * is just run. It could be improved making it possible to enable or
	     * disable this behaviour through a variable in dust.conf for example.
	     * Another improve could be make this update in the loop above so we
	     * make just one loop, not two.
	     * **/
	    // Update the Blogs.
		subs_comp = (SubscriptionComposite)bottom_comp.getChildren()[SUBSCRIPTION_VIEW];
		blogs_tree = subs_comp.getBlogsTree();
		for (int i = 0; i < blogs_tree.getItemCount(); i++) {
			if (blogs_tree.getItem(i).getData() instanceof BlogController) {
				blog_ctrl = (BlogController)blogs_tree.getItem(i).getData();
				blog_ctrl.forceUpdate();
			}
		}
		
		/**
		 * TODO: The period of every update is hardcoded. This will be much
		 * more flexible if we can control it through a configuration variable.
		 * It is not hard do it, but I have no time right now.
		 */
		// Set a timer to update blogs silently every hour.
		updates_timer = new Timer(); 
		updates_timer.schedule(new UpdatePeriodicallyTask(), HOUR, HOUR);
	}
	
	/**
	 * Make the interface start listen for events and dispatch them.
	 */
	public void runIface() {
		try {
			// Draw everything inside this shell.
			shell.open();
			shell.forceActive();
		
			// Listen for events and do the work.
			while (!shell.isDisposed()) {
				if (!shell.getDisplay().readAndDispatch())
					shell.getDisplay().sleep();
			}
		}
		catch (Exception e) {
			ExceptionBox ebox;
			ebox = new ExceptionBox(e, new Shell());
			ebox.popError();
		}
	}
}
